package utilityTools;

import org.geotools.data.DataStore;
import org.geotools.data.DataStoreFinder;
import org.geotools.data.shapefile.ShapefileDataStore;
import org.geotools.data.shapefile.ShapefileDataStoreFactory;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.referencing.crs.CoordinateReferenceSystem;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Serializable;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.*;

public class ShpReaderUtil {
    //后期考虑把shp的创建也放在这里。
    //另外需要注意：shp在创建时的字段长度不能大于10个英文字符，一定要注意！

    /**
     * 读取shp数据（只可能是本地文件，如果来源于数据库就是另外的格式）
     *
     * @param shpfile shp本地文件的路径
     * @return
     * @throws Exception
     */
    public static SimpleFeatureCollection getShpfileData(File shpfile) throws Exception {
        String enctype = "utf8";
        SimpleFeatureSource featureSourceShp = getShpfileSource(shpfile, enctype);
        return featureSourceShp.getFeatures();
    }

    /**
     * 打开shp文件并返回FeatureSource对象
     * （Java中是通过方法重载间接实现了函数默认值）
     *
     * @param shpfile shapefile文件对象
     * @return
     * @throws Exception
     */
    public static SimpleFeatureSource getShpfileSource(File shpfile) throws Exception {
        String enctype = "utf8";//规定默认是使用utf8的编码方式
        return getShpfileSource(shpfile, enctype);
    }

    /**
     * 打开shp文件并返回FeatureSource对象
     *
     * @param shpfile shp文件流对象
     * @param enctype 编码方式
     * @return
     * @throws Exception
     */
    public static SimpleFeatureSource getShpfileSource(File shpfile, String enctype) throws Exception {
        Map<String, Object> parms = new HashMap<>();
        parms.put("url", shpfile.toURI().toURL());
        DataStore shpDataStore = DataStoreFinder.getDataStore(parms);
        ((ShapefileDataStore) shpDataStore).setCharset(Charset.forName(enctype));
        String typeName = shpDataStore.getTypeNames()[0];
        SimpleFeatureSource featureSourceShp = shpDataStore.getFeatureSource(typeName);
        return featureSourceShp;
    }

    /**
     * 返回新的字段对应关系（除Geometry字段之外）
     *
     * @param fieldsSourceNoGeometry 原始shp的属性字段列表
     * @return Map<sourcefield, newfield>
     */
    public static Map<String, String> getFieldMapping(List<String> fieldsSourceNoGeometry) {
        Map<String, String> fieldMapping = new HashMap<>();
        int cnts = 1;
        for (String fieldSource : fieldsSourceNoGeometry) {
            if (fieldSource.length() < 10) {
                fieldMapping.put(fieldSource, fieldSource);
            } else {//需要处理两种情况：一是自身超过10个字符需要截取，另一个是截取之后是否有重复的
                String fieldNew = fieldSource.substring(0, 9) + "_";
                if (fieldMapping.containsKey(fieldNew)) {
                    fieldNew = fieldSource.substring(0, 8) + "_" + cnts;
                    cnts++;
                }
                fieldMapping.put(fieldSource, fieldNew);
            }
        }
        return fieldMapping;
    }

    public static Map<String, String> getFieldMapping(SimpleFeatureType typeSource) {
        Map<String, String> fieldMapping = new HashMap<>();
        String fieldGeometry = typeSource.getGeometryDescriptor().getName().toString();
        List<String> attrsSourceWithoutGeo = new ArrayList<>();
        for (AttributeDescriptor attrSource : typeSource.getAttributeDescriptors()) {
            if (attrSource.getName().toString().equalsIgnoreCase(fieldGeometry)) {
                continue;
            }
            attrsSourceWithoutGeo.add(attrSource.getName().toString());
        }
        return getFieldMapping(attrsSourceWithoutGeo);
    }

    /**
     * 根据数据源创建目标shp文件（结构和原来的完全一样，包括属性和几何类型）
     *
     * @param newNameNoExtent 文件名(不含扩展名)
     * @param sfcSource       原始数据集
     * @return
     * @throws IOException
     */
    public static EnumMap<CustomizeType, Object> createNewShp(String newNameNoExtent,
                                                              SimpleFeatureCollection sfcSource) throws IOException {
        SimpleFeatureType typeSource = sfcSource.getSchema();
        List<String> fieldsTarget = new ArrayList<>();
        for (AttributeDescriptor attributeDescriptor : typeSource.getAttributeDescriptors()) {
            if (attributeDescriptor.getLocalName().equalsIgnoreCase(typeSource.getGeometryDescriptor().getLocalName())) {
                continue;
            }
            //待测试这里的类型获取是否成功
            fieldsTarget.add(attributeDescriptor.getName().toString());
        }
        Class<?> geoType = typeSource.getGeometryDescriptor().getType().getClass();
        return createNewShp(newNameNoExtent, fieldsTarget, geoType, typeSource.getCoordinateReferenceSystem());
    }

    /**
     * 根据原始数据集创建新的shp文件（只保留属性结构，改变几何类型）
     *
     * @param newNameNoExtent 文件名（不含扩展名）
     * @param sfcSource       原始数据集
     * @param geoType         新的shp的几何类型
     * @return
     * @throws IOException
     */
    public static EnumMap<CustomizeType, Object> createNewShp(String newNameNoExtent,
                                                              SimpleFeatureCollection sfcSource, Class<?> geoType) throws IOException {
        SimpleFeatureType typeSource = sfcSource.getSchema();
        List<String> fieldsTarget = new ArrayList<>();
        for (AttributeDescriptor attributeDescriptor : typeSource.getAttributeDescriptors()) {
            if (attributeDescriptor.getLocalName().equalsIgnoreCase(typeSource.getGeometryDescriptor().getLocalName())) {
                continue;
            }
            //待测试这里的类型获取是否成功
            fieldsTarget.add(attributeDescriptor.getName().toString());
        }
        return createNewShp(newNameNoExtent, fieldsTarget, geoType, typeSource.getCoordinateReferenceSystem());
    }

    /**
     * 创建新的shp文件
     *
     * @param newNameNoExtent           文件名（不含扩展名）
     * @param fieldsTarget              属性列结构
     * @param geoType                   几何类型
     * @param coordinateReferenceSystem 坐标参考系统(可以为空,默认为wgs84)
     * @return
     * @throws IOException
     */
    public static EnumMap<CustomizeType, Object> createNewShp(String newNameNoExtent, List<String> fieldsTarget, Class<?> geoType, CoordinateReferenceSystem coordinateReferenceSystem) throws IOException {
        String pathTarget = getNewFileName(newNameNoExtent);
        File shpNew = new File(pathTarget);
        Map<String, Serializable> parms = new HashMap<>();
        parms.put(ShapefileDataStoreFactory.URLP.key, shpNew.toURI().toURL());
        parms.put("create spatial index", Boolean.TRUE);
        ShapefileDataStoreFactory factory = new ShapefileDataStoreFactory();
        ShapefileDataStore shpDS = ((ShapefileDataStore) factory.createNewDataStore(parms));
        //创建Shp文件的DataStore对象
        SimpleFeatureTypeBuilder tbNew = new SimpleFeatureTypeBuilder();
        //坐标参考默认为wgs84
        CoordinateReferenceSystem targetCoordReference =
                coordinateReferenceSystem == null ? DefaultGeographicCRS.WGS84 : coordinateReferenceSystem;
        tbNew.setCRS(targetCoordReference);
        tbNew.setName(newNameNoExtent);
        tbNew.add("the_geom", geoType);
        for (String attr : fieldsTarget) {
            tbNew.add(attr.trim(), String.class);
        }
        shpDS.createSchema(tbNew.buildFeatureType());
//        shpDS.setCharset(StandardCharsets.UTF_8);
        EnumMap<CustomizeType, Object> rslt = new EnumMap<CustomizeType, Object>(CustomizeType.class);
        rslt.put(CustomizeType.ShpPath, pathTarget);
        rslt.put(CustomizeType.ShpDataStore,shpDS);
        return rslt;
    }

    //生成新的文件
    public static String getNewFileName(String nameNoExtent) throws FileNotFoundException {
        String pathExcel = "E:\\Test\\" + nameNoExtent + ".shp";
        File file = new File(pathExcel);
        if (file.exists()) {
            return getNewFileName(nameNoExtent + "_1");
        }
        return pathExcel;
    }
}
